﻿package me.zhengjie.common.config

//import org.springframework.data.redis.serializer.StringRedisSerializer

import cn.hutool.core.lang.Assert
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.parser.ParserConfig
import com.alibaba.fastjson.serializer.SerializerFeature
import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import me.zhengjie.logging.slf4j.*
import org.apache.commons.codec.digest.DigestUtils
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.data.redis.RedisProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.Cache
import org.springframework.cache.CacheManager
import org.springframework.cache.annotation.CachingConfigurer
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.interceptor.CacheErrorHandler
import org.springframework.cache.interceptor.KeyGenerator
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.cache.RedisCacheConfiguration
import org.springframework.data.redis.cache.RedisCacheManager
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisOperations
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.RedisSerializationContext
import org.springframework.data.redis.serializer.RedisSerializer
import org.springframework.data.redis.serializer.StringRedisSerializer
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
import java.lang.reflect.Method
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.time.Duration


/**
 * @author Kuki Wu
 * @date 2024-11-24
 */
@Configuration
@EnableCaching
@ConditionalOnClass(
    RedisOperations::class
)
@EnableConfigurationProperties(RedisProperties::class)
@EnableRedisRepositories(basePackages = ["me.zhengjie.modules.mnt.repository"])
//class RedisConfig : CachingConfigurerSupport() {
class RedisConfig : CachingConfigurer {

    @Bean
    fun cacheManager(redisConnectionFactory: RedisConnectionFactory): CacheManager =
        RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
            RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(6))
                //.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
                .serializeValuesWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(
                        Jackson2JsonRedisSerializer(ObjectMapper().apply {
//                            setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
//                            activateDefaultTyping(
//                                LaissezFaireSubTypeValidator.instance,
//                                ObjectMapper.DefaultTyping.NON_FINAL
//                            )
//                        }, Any::class.java)
                            setSerializationInclusion(JsonInclude.Include.ALWAYS)// 不为 null 才序列化
                            configure(SerializationFeature.INDENT_OUTPUT, true) // 美化格式
                            setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) // 可见性，只序列化任意修饰符的字段
                            disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 关闭某个特征
                            registerKotlinModule() // 注册 Kotlin 模块，支持 Kotlin 数据类
                            activateDefaultTyping(
                                LaissezFaireSubTypeValidator.instance,//  验证器，用于验证实际要反序列化的子类型是否有效
                                ObjectMapper.DefaultTyping.NON_FINAL, // 定义哪些类型的对象需要添加额外的类型信息，NON_FINAL：非 final类都会包含
                                JsonTypeInfo.As.PROPERTY // 类型信息的包含方式 PROPERTY：类型信息作为 JSON 对象的一个属性
                            )
                        }, Any::class.java)
                    )
                )
                .disableCachingNullValues()
        ).build()

    @Bean(name = ["redisTemplate"])
    @ConditionalOnMissingBean(name = ["redisTemplate"])
    fun redisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<Any, Any> =
        RedisTemplate<Any, Any>().apply {
            val jackson2JsonRedisSerializer = Jackson2JsonRedisSerializer(ObjectMapper().apply {
                setSerializationInclusion(JsonInclude.Include.NON_NULL)// 不为 null 才序列化
                configure(SerializationFeature.INDENT_OUTPUT, true) // 美化格式
                setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) // 可见性，只序列化任意修饰符的字段
                disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 关闭某个特征
                //setDateFormat(SimpleDateFormat("yyyy-MM-dd"))
                registerKotlinModule() // 注册 Kotlin 模块，支持 Kotlin 数据类
                activateDefaultTyping(
                    LaissezFaireSubTypeValidator.instance,//  验证器，用于验证实际要反序列化的子类型是否有效
                    ObjectMapper.DefaultTyping.NON_FINAL, // 定义哪些类型的对象需要添加额外的类型信息，NON_FINAL：非 final类都会包含
                    JsonTypeInfo.As.PROPERTY // 类型信息的包含方式 PROPERTY：类型信息作为 JSON 对象的一个属性
                    //JsonTypeInfo.As.WRAPPER_OBJECT
                )
            }, Any::class.java)

            setConnectionFactory(redisConnectionFactory)
            keySerializer = StringRedisSerializer()
            valueSerializer = jackson2JsonRedisSerializer
            hashValueSerializer = jackson2JsonRedisSerializer
        }

    //final var log = org.slf4j.LoggerFactory.getLogger(RedisConfig::class.java)
//    @Bean
//    fun cacheManager(redisConnectionFactory: RedisConnectionFactory): CacheManager {
//        val fastJsonRedisSerializer: FastJsonRedisSerializer<Any> =
//            FastJsonRedisSerializer<Any>(Any::class.java)
//       return  RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
//            RedisCacheConfiguration.defaultCacheConfig()
//                .entryTtl(Duration.ofHours(6))
//                //.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer())
//                .serializeValuesWith(
//                    RedisSerializationContext.SerializationPair.fromSerializer(
//                        fastJsonRedisSerializer
///*                        Jackson2JsonRedisSerializer(ObjectMapper().apply {
//                            setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
//                            activateDefaultTyping(
//                                LaissezFaireSubTypeValidator.instance,
//                                ObjectMapper.DefaultTyping.NON_FINAL
//                            )
//                        }, Any::class.java)*/
//                    )
//                )
//                //.disableCachingNullValues()
//        ).build()
//    }
//    @Bean(name = ["redisTemplate"])
//    @ConditionalOnMissingBean(name = ["redisTemplate"])
//    fun redisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<Any, Any> =
//        RedisTemplate<Any, Any>().apply {
//            val jackson2JsonRedisSerializer = Jackson2JsonRedisSerializer(ObjectMapper().apply {
//                setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
//                activateDefaultTyping(
//                    LaissezFaireSubTypeValidator.instance,
//                    ObjectMapper.DefaultTyping.NON_FINAL
//                )
//            }, Any::class.java)
//
//            setConnectionFactory(redisConnectionFactory)
//            keySerializer = StringRedisSerializer()
//            valueSerializer = jackson2JsonRedisSerializer
//            hashValueSerializer = jackson2JsonRedisSerializer
//        }


    /**
     * 设置 redis 数据默认过期时间，默认6小时
     * 设置@cacheable 序列化方式
     */
//    @Bean
//    fun redisCacheConfiguration(): RedisCacheConfiguration {
//        val fastJsonRedisSerializer = FastJsonRedisSerializer(Any::class.java)
//        var configuration = RedisCacheConfiguration.defaultCacheConfig()
//        configuration = configuration.serializeValuesWith(
//            RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)
//        ).entryTtl(
//            Duration.ofHours(6)
//        )
//        return configuration
//    }
//
//    @Bean(name = ["redisTemplate"])
//    @ConditionalOnMissingBean(name = ["redisTemplate"])
//    fun redisTemplate(redisConnectionFactory: RedisConnectionFactory?): RedisTemplate<Any, Any> {
//        val template = RedisTemplate<Any, Any>()
//        //序列化
//        val fastJsonRedisSerializer = FastJsonRedisSerializer(Any::class.javaObjectType)
//        // value值的序列化采用fastJsonRedisSerializer
//        template.valueSerializer = fastJsonRedisSerializer
//        template.hashValueSerializer = fastJsonRedisSerializer
//        // 全局开启AutoType，这里方便开发，使用全局的方式
//        ParserConfig.getGlobalInstance().setAutoTypeSupport(true)
//        // 建议使用这种方式，小范围指定白名单
////        ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain");
////        ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.service.dto");
////        ParserConfig.getGlobalInstance().addAccept("me.zhengjie.service.dto");
////        ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.system.domain");
////        ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.quartz.domain");
////        ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.monitor.domain");
////        ParserConfig.getGlobalInstance().addAccept("me.zhengjie.modules.security.security");
//        // key的序列化采用StringRedisSerializer
//        template.keySerializer = StringRedisSerializer()
//        template.hashKeySerializer = StringRedisSerializer()
//        template.connectionFactory = redisConnectionFactory
//
//        // 初始化模板
//        template.afterPropertiesSet()
//        return template
//    }

    /**
     * 自定义缓存key生成策略，默认将使用该策略
     */
    @Bean
    override fun keyGenerator(): KeyGenerator? {
        return KeyGenerator { target: Any, method: Method, params: Array<Any> ->
            val container: MutableMap<String, Any> =
                HashMap()
            val targetClassClass: Class<*> = target.javaClass
            // 类地址
            container["class"] = targetClassClass.toGenericString()
            // 方法名称
            container["methodName"] = method.name
            // 包名称
            container["package"] = targetClassClass.getPackage()
            // 参数列表
            for (i in params.indices) {
                container[i.toString()] = params[i]
            }
            // 转为JSON字符串
            val jsonString: String = JSON.toJSONString(container)
            DigestUtils.sha256Hex(jsonString)
        }
    }
//    override fun cacheResolver(): CacheResolver? {
//        return SimpleCacheResolver(cacheManager()!!)
//    }

    @Bean
    override fun errorHandler(): CacheErrorHandler? {
        // 异常处理，当Redis发生异常时，打印日志，但是程序正常走
        logInfo("初始化 -> [Redis CacheErrorHandler]")
        return object : CacheErrorHandler {
            override fun handleCacheGetError(e: RuntimeException, cache: Cache, key: Any) {
               logError("Redis occur handleCacheGetError：key -> [{${key}}]\n"+e.message)
            }

            override fun handleCachePutError(e: RuntimeException, cache: Cache, key: Any, value: Any?) {
                logError(
                    "Redis occur handleCachePutError：key -> [{${key}}]；value -> [{${value}}]\n"+e.message
                )
            }

            override fun handleCacheEvictError(e: RuntimeException, cache: Cache, key: Any) {
                logError(
                    "Redis occur handleCacheEvictError：key -> [{${key}}]\n"+e.message
                )
            }

            override fun handleCacheClearError(e: RuntimeException, cache: Cache) {
                logError("Redis occur handleCacheClearError："+e.message)
            }
        }
    }
}

///**
// * Value 序列化
// *
// * @author /
// * @param <T>
//</T> */
//internal class FastJsonRedisSerializer<T>(private val clazz: Class<T>) : RedisSerializer<T?> {
//    override fun serialize(t: T?): ByteArray? {
//        return if (t == null) {
//            ByteArray(0)
//        } else JSON.toJSONString(t, SerializerFeature.WriteClassName).toByteArray(StandardCharsets.UTF_8)
//    }
//
//    override fun deserialize(bytes: ByteArray?): T? {
//        if (bytes == null || bytes.size <= 0) {
//            return null
//        }
//        val str = String(bytes, StandardCharsets.UTF_8)
//        return JSON.parseObject(str, clazz)
//    }
//}


///**
// * 重写序列化器
// *
// * @author /
// */
internal class StringRedisSerializer private constructor(charset: Charset) :
    RedisSerializer<Any?> {
    private val charset: Charset

    constructor() : this(StandardCharsets.UTF_8)

    init {
        Assert.notNull(charset, "Charset must not be null!")
        this.charset = charset
    }

    override fun deserialize(bytes: ByteArray?): String? {
        return if (bytes == null) null else String(bytes, charset)
    }

    override fun serialize(`object`: Any?): ByteArray? {
        var string: String = JSON.toJSONString(`object`)
        if (org.apache.commons.lang3.StringUtils.isBlank(string)) {
            return null
        }
        string = string.replace("\"", "")
        return string.toByteArray(charset)
    }
}
